GSoC23 — Workweek 8

Introduction

I am currently in the middle of implementing the SDF INTERCONNECT feature and while there was fast progress early on things have now stalled.

What is the issue? Let me explain this by showing you how I intended to annotate the design.

Interconnect - VPI

We start at the VPI side, where $sdf_annotate is called to start the process of SDF annotation. Once a INTERCONNECT token is parsed, we call sdf_interconnect_delays with the appropriate parameters.

(INTERCONNECT A0 input1.A (0.014:0.014:0.014) (0.006:0.006:0.006))

(An example of an interconnect annotation)

This function finds the port handles in the respective scopes and requests an intermodpath via vpi_handle_multi and the ports as arguments. Once a vpiInterModPath is returned, the delays are put with vpi_put_delays and the annotation is complete.

So far no issues, this all works well. But all the magic lies in vpi_handle_multi which is implemented on the VVP side.

Interconnect - VVP

We received two port handles via vpi_handle_multi and have to find the common net between those (if it even exists). How do we do this?

This is the approach which I have used until now:

We can't go from vpiPort to its net, as this information is missing. But we know that the corresponding net in the module has the same name as the port. Therefore iterate over all nets in each module vpi_iterate(vpiNet, mod); and find the matching nets for the ports.

Okay, now we have two vpiNets as vpiHandles, we can cast them to a __vpiSignal, the internal representation of a vpiNet.

struct __vpiSignal*net_node1 = dynamic_cast<__vpiSignal*>(net_handle1);

We can then directly access the vvp_net_t like this: vvp_net_t* my_node1 = net_node1->node;

Now we have both vvp_net_t pointers for both ports, we can just compare the addresses if they are the same.

Now we need to somehow insert our delay element without breaking the network.

So we take whatever is connected to the vvp_net_t and connect it to our new vvp_net_t with vvp_fun_intermodpath. The new net is then connected to the first net to complete the network.

Okay, vvp_fun_intermodpath is where we place our delays. This means we must be able to reference it later on. For this we create the __vpiInterModPath class which holds the reference to its vvp_fun_intermodpath. We instantiate such an object and return it from vpi_handle_multi.

Now, when vpi_put_delays is called with a __vpiInterModPath, then we just set the delay of the associated vvp_fun_intermodpath. Done!

The Issue with the Current Approach

So why does this approach have issues?

Actually I was able to annotate a simple design with three buffers in series just fine. The network looks like this:

     (reg a;)         (buffer instance) (buffer instance) (buffer instance)
vvp_fun_signal_vec ---> vvp_fun_bufz ---> vvp_fun_bufz ---> vvp_fun_bufz --->

Three vvp_fun_intermodpaths get successfully inserted between the nodes.

But if you have two ore more of these buffers in parallel things fall apart:

     (reg a;)         (buffer instances)
vvp_fun_signal_vec ---> vvp_fun_bufz
                    |-> vvp_fun_bufz
                    |-> vvp_fun_bufz

You see, we only get a reference to vvp_fun_signal_vec with this approach. Therefore we don't know which vvp_fun_signal_vec <-> vvp_fun_bufz intermodpath we are currently annotating

This becomes even more complicated with specify blocks enabled:

     (reg a;)          (modpaths and buffers)
vvp_fun_signal_vec ---> vvp_fun_modpath_src
                    |-> vvp_fun_modpath_src
                    |-> vvp_fun_modpath_src
                    |-> vvp_fun_bufz
                    |-> vvp_fun_bufz
                    |-> vvp_fun_bufz

It's just not clear with the information available to us.

Proposal

My idea to solve this particular problem is to add a vvp_net_t for each input and output of a module with a vvp_fun_port functor.

This will improve the following points:

  • The lookup from vpiPort to vvp_net_t becomes much easier, as the information to which vvp_net_t the port is connected can be stored in vpiPortInfo.
  • I can easily check whether the two port functors are connected to each other just by looking at the output ptr of the first one.
  • If there is a vvp_fun_intermodpath after the first port and the second port is connected to this same vvp_fun_intermodpath, I know that I have already inserted a vvp_fun_intermodpath and can simply put the delay.
  • As a bonus, we can easily implement PORT delays by putting a delay in vvp_fun_port.

The netlist would then look like this:

     (reg a;)          (input port        (input port
                          my_design)          buffers)
vvp_fun_signal_vec ---> vvp_fun_port ---> vvp_fun_port ---> vvp_fun_modpath_src
                                      |                  |-> vvp_fun_bufz
                                      |
                                      |-> vvp_fun_port ---> vvp_fun_modpath_src
                                      |                 |-> vvp_fun_bufz
                                      |
                                      |-> vvp_fun_port ---> vvp_fun_modpath_src
                                                        |-> vvp_fun_bufz

As you can see, the connection to each module is through a vvp_fun_port. This would solve the problem from before where I didn't know which vvp_fun_modpath_src and vvp_fun_bufz belong to the given port.


The VVP code generated for one of the buffers currently looks like this:

S_0x55fe0ca10970 .scope module, "buffer0" "buffer" 2 21, 2 3 0, S_0x55fe0ca10790;
 .timescale -9 -12;
    .port_info 0 /INPUT 1 "in";
    .port_info 1 /OUTPUT 1 "out";
L_0x55fe0ca25c30 .functor BUFZ 1, v0x55fe0ca25ac0_0, C4<0>, C4<0>, C4<0>;
v0x55fe0ca10b50_0 .net "in", 0 0, v0x55fe0ca25ac0_0;  alias, 1 drivers
v0x55fe0ca24d70_0 .net "out", 0 0, L_0x55fe0ca25c30;  alias, 1 drivers

I would propose to change it to this:

S_0x556f997bd970 .scope module, "buffer0" "buffer" 2 21, 2 3 0, S_0x556f997bd790;
 .timescale -9 -12;
    .port_info 0 /INPUT 1 "in" L_0x123412341234;
    .port_info 1 /OUTPUT 1 "out" L_0x234234234234;
L_0x123412341234 .functor PORT 1, v0x556f997d2ac0_0, C4<0>, C4<0>, C4<0>;
L_0x234234234234 .functor PORT 1, L_0x556f997d2c30, C4<0>, C4<0>, C4<0>;
L_0x556f997d2c30 .functor BUFZ 1, L_0x123412341234, C4<0>, C4<0>, C4<0>;
v0x556f997bdb50_0 .net "in", 0 0, v0x556f997d2ac0_0;  alias, 1 drivers
v0x556f997d1d70_0 .net "out", 0 0, L_0x556f997d2c30;  alias, 1 drivers

All input/output signals go through the PORT functor, also the .port_info statement would get an additional parameter, the label for the corresponding PORT functor. Also I would have to make sure that the modpaths are connected correctly when -gspecify is enabled.

The drawbacks to this new approach are:

  1. Small performance overhead, because all signals going from or to a module must now pass through an additional buffer.

  2. The connections inside the modules have to be drastically changed, which means a lot of work in IVL.

Summary

I am currently awaiting a reply from one of the core developers whether this is the right way to go.

Until then I am trying to think of different solutions and try to familiarize myself more with IVL.

See you next week!